package com.fary.beans.factory.xml;

import com.fary.beans.BeanUtils;
import com.fary.core.SpringException;
import com.fary.beans.factory.parsing.*;
import com.fary.beans.factory.support.AbstractBeanDefinitionReader;
import com.fary.beans.factory.support.BeanDefinitionRegistry;
import com.fary.core.NamedThreadLocal;
import com.fary.core.env.Environment;
import com.fary.core.io.Resource;
import com.fary.core.io.ResourceLoader;
import com.fary.core.io.support.EncodedResource;
import com.fary.core.io.support.ResourcePatternResolver;
import com.fary.util.Assert;
import com.fary.util.xml.XmlValidationModeDetector;
import org.w3c.dom.Document;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;

import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

    // 存放当前线程正在加载的配置文件集合
    private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded = new NamedThreadLocal<>("XML bean definition resources currently being loaded");

    // 解析xml返回Document对象
    private DocumentLoader documentLoader = new DefaultDocumentLoader();

    // 解析xml具体策略（DTD、schema）
    private EntityResolver entityResolver;
    // 解析xml错误处理器
    private ErrorHandler errorHandler = new SimpleSaxErrorHandler();

    private int validationMode = VALIDATION_AUTO;

    // 表示应禁用验证。
    public static final int VALIDATION_NONE = XmlValidationModeDetector.VALIDATION_NONE;
    // 表示应自动检测验证模式。
    public static final int VALIDATION_AUTO = XmlValidationModeDetector.VALIDATION_AUTO;
    // 指示应使用 DTD 验证
    public static final int VALIDATION_DTD = XmlValidationModeDetector.VALIDATION_DTD;
    // 指示应使用 XSD 验证。
    public static final int VALIDATION_XSD = XmlValidationModeDetector.VALIDATION_XSD;
    // 检测验证模式
    private final XmlValidationModeDetector validationModeDetector = new XmlValidationModeDetector();

    private boolean namespaceAware = false;

    // 默认的xml文档bean定义阅读器
    private Class<? extends BeanDefinitionDocumentReader> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;
    // xml阅读问题报告
    private ProblemReporter problemReporter = new FailFastProblemReporter();
    // xml阅读事件监听
    private ReaderEventListener eventListener = new EmptyReaderEventListener();
    // 具体指定的源文件
    private SourceExtractor sourceExtractor = new NullSourceExtractor();
    // 命名空间标签处理器
    private NamespaceHandlerResolver namespaceHandlerResolver;

    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
        super(registry);
    }

    public boolean isNamespaceAware() {
        return this.namespaceAware;
    }

    /**
     * 获取验证模式
     */
    public int getValidationMode() {
        return this.validationMode;
    }

    /**
     * 设置验证模式
     */
    public void setValidating(boolean validating) {
        this.validationMode = (validating ? VALIDATION_AUTO : VALIDATION_NONE);
        this.namespaceAware = !validating;
    }

    /**
     * 获取解析器
     */
    protected EntityResolver getEntityResolver() {
        if (this.entityResolver == null) {
            // Determine default EntityResolver to use.
            ResourceLoader resourceLoader = getResourceLoader();
            if (resourceLoader != null) {
                this.entityResolver = new ResourceEntityResolver(resourceLoader);
            } else {
                this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
            }
        }
        return this.entityResolver;
    }

    /**
     * 设置一个解析器
     */
    public void setEntityResolver(EntityResolver entityResolver) {
        this.entityResolver = entityResolver;
    }









    @Override
    public int loadBeanDefinitions(Resource resource) throws SpringException {
        return loadBeanDefinitions(new EncodedResource(resource));
    }

    /**
     * 加载xml配置文件
     *
     * @param encodedResource 编码资源
     */
    public int loadBeanDefinitions(EncodedResource encodedResource) throws SpringException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");

        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
            currentResources = new HashSet<>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        if (!currentResources.add(encodedResource)) {
            throw new SpringException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
        try {
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            } finally {
                inputStream.close();
            }
        } catch (IOException ex) {
            throw new SpringException("IOException parsing XML document from " + encodedResource.getResource(), ex);
        } finally {
            currentResources.remove(encodedResource);
            if (currentResources.isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
    }


    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws SpringException {
        try {
            Document doc = doLoadDocument(inputSource, resource);
            int count = registerBeanDefinitions(doc, resource);
            return count;
        } catch (Exception ex) {
            throw new SpringException("fail parsing XML document", ex);
        }
    }

    /**
     * 加载xml文档对象
     *
     * @param inputSource
     * @param resource
     * @return
     * @throws Exception
     */
    protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
        return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                getValidationModeForResource(resource), isNamespaceAware());
    }


    /**
     * 注册bean定义
     */
    public int registerBeanDefinitions(Document doc, Resource resource) throws SpringException {
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        int countBefore = getRegistry().getBeanDefinitionCount();
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }

    /**
     * 获取默认的xml文档bean定义阅读器
     */
    protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
        return BeanUtils.instantiateClass(this.documentReaderClass);
    }

    public XmlReaderContext createReaderContext(Resource resource) {
        return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
                this.sourceExtractor, this, getNamespaceHandlerResolver());
    }

    /**
     * 获取命名空间处理器
     */
    public NamespaceHandlerResolver getNamespaceHandlerResolver() {
        if (this.namespaceHandlerResolver == null) {
            this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
        }
        return this.namespaceHandlerResolver;
    }

    /**
     * 获取默认的命名空间处理器
     *
     * @return
     */
    protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
        ClassLoader cl = (getResourceLoader() != null ? getResourceLoader().getClassLoader() : getBeanClassLoader());
        return new DefaultNamespaceHandlerResolver(cl);
    }


    /**
     * 获取验证模式
     * 如果设置自动检测验证模式，
     */
    protected int getValidationModeForResource(Resource resource) {
        int validationModeToUse = getValidationMode();
        if (validationModeToUse != VALIDATION_AUTO) {
            return validationModeToUse;
        }
        int detectedMode = detectValidationMode(resource);
        if (detectedMode != VALIDATION_AUTO) {
            return detectedMode;
        }
        return VALIDATION_XSD;
    }



    protected int detectValidationMode(Resource resource) {
        if (resource.isOpen()) {
            throw new SpringException(
                    "Passed-in Resource [" + resource + "] contains an open stream: " +
                            "cannot determine validation mode automatically. Either pass in a Resource " +
                            "that is able to create fresh streams, or explicitly specify the validationMode " +
                            "on your XmlBeanDefinitionReader instance.");
        }

        InputStream inputStream;
        try {
            inputStream = resource.getInputStream();
        } catch (IOException ex) {
            throw new SpringException("Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
                    "Did you attempt to load directly from a SAX InputSource without specifying the " +
                    "validationMode on your XmlBeanDefinitionReader instance?", ex);
        }

        try {
            return this.validationModeDetector.detectValidationMode(inputStream);
        } catch (IOException ex) {
            throw new SpringException("Unable to determine validation mode for [" + resource + "]: an error occurred whilst reading from the InputStream.", ex);
        }
    }



    @Override
    public int loadBeanDefinitions(String location) throws SpringException {
        return loadBeanDefinitions(location, null);
    }

    @Override
    public int loadBeanDefinitions(String... locations) throws SpringException {
        Assert.notNull(locations, "Location array must not be null");
        int count = 0;
        for (String location : locations) {
            count += loadBeanDefinitions(location);
        }
        return count;
    }

}